# -*- coding: utf-8 -*-
#
#    Copyright (C) 2012 Gonzalo Bonigo <gonzalobonigo@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see http://www.gnu.org/licenses/
#


"""
    Este modulo exporta un conjunto de funciones utiles para todo el paquete lts.
"""

__version__ = 3.1

__all__ =    [
    "show_nondiag_info",
    "share_all_events",
    "check_sync_type",
    "path_to_string",
    "tp_to_sys_path",
    "stack_to_path",
    "share_events",
    "SYNC_PARTIAL",
    "SYNC_EMPTY",
    "SYNC_FULL",
    "str_time",
    "graph"
]


import sys
import os

import lts


# TODO: Please make it an enumeration.
# Tipos de sincronizacion.
SYNC_EMPTY = 0
SYNC_PARTIAL = 1
SYNC_FULL = 2

def str_time (t):
    """
        Descripcion:
            Convierte el arguemtno "t", dado en segundos, en un string.
            Si al cantidad de segundos es mayor a 60 los convierte en minutos,
            si es mayor a 3600 los convierte en horas.
        Agumentos:
            t  -  Un numero que reprecente una cantidad de tiempo en segundos.
                  Usualmente es el valor de retorno de la funcion time.time().
        Retorno:
            Devuelve un string con alguno de los siguientes formatos:
                "xx.yy seconds."
                "xx.yy minutes."
                "xx.yy hours."
    """
    if t > 60.0:
        t = t/60.0
    else:
        return str(t)+" seconds."
    if t > 60.0:
        t = t/60.0
    else:
        return str(t)+" minutes."
    return str(t)+" hours."

def check_sync_type (system, sync_set):
    """
        Descripcion:
            Esta funcion dice cual es el tipo de sincronizacion que hay entre
            los subsistemas.
        Argumentos:
            system    - Instancia de la clase Set() que contiene el conjunto de
                        subsistemas, cada uno de estos una instancia de la clase
                        lts.LTS().
            sync_set  - Instancia de la clase Set() que contiene el conjunto de
                        eventos en los que se quiere sincronizar.
        Retorno:
            SYNC_EMPTY    -  Si el tipo de sincronizacion es vacia.
            SYNC_PARTIAL  -  Si el tipo de sincronizacion es parcial.
            SYNC_FULL     -  Si el tipo de sincronizacion es total.
    """
    # Dice si hay al menos dos subsistemas que tengan eventos en comun.
    some_share = False
    # Almacena la interseccion de los eventos observables de todos los
    # subsistemas.
    inter_set = system[0].get_event_set('o')
    for subsystem in system:
        inter_set = inter_set.intersection(subsystem.get_event_set('o'))
        if not some_share:
            for s in system:
                if s != subsystem and share_events(subsystem, s):
                    some_share = True
                    break
    if not sync_set or (not inter_set and not some_share):
        return SYNC_EMPTY
    elif inter_set.issubset(sync_set):
        return SYNC_FULL
    else:
        return SYNC_PARTIAL

def show_nondiag_info (subsystem, diag_res, graphic):
    """
        Descripcion:
            Esta es una funcion auxiliar de "_mode_0()" que se encarga de
            mostrar por pantalla la informacion de un subsistema no
            diagnosticable.
        Arguemtnos:
            subsystem  -  Una instancia de la clase lts.LTS() de la cual se
                          va a mostrar la informacion.
            diag_res   -  Resultado del analisis de diagnosticabilidad de
                          "subsistem". Es el valor de retorno del metodo
                          "subsistem.is_diagnosable()".
            graphic    -  Es un booleano que dice si se debe graficar el
                          subsistema y su twin-plant o no.
        Retorno:
            No tiene.
    """
    subsystem_tp = subsystem.get_twinplant()
    map_dict = {subsystem:lts.LTS.to_file(subsystem)}
    map_dict[subsystem_tp] = lts.LTS.to_file(subsystem_tp)
    print "\nThe subsystem",subsystem.name,"has the following " + \
          "non-diagnosable faults:"
    for (f,path) in diag_res['fault_list']:
        print "* Fault:", f
        print "- Undecidible path:"
        print "\t", path_to_string(path, subsystem_tp, map_dict)
        subsys_paths = tp_to_sys_path(path, subsystem, map_dict)
        print "- Undesirable pair:"
        print '\t', subsys_paths[0]
        print '\t', subsys_paths[1]
    if graphic:
        graph(subsystem.name)
        graph(subsystem_tp.name)

def share_events (s1, s2, sync_set=None):
    """
        Descripcion:
            Esta funcion dice si dos sistemas comparten eventos de
            sincronizacion. Si "sync_set" se espesifica dice si los sistemas
            comparten eventos de sincronizacion que esten en "sync_set".
        Argumentos:
            s1,s2    -  Instancias de la clase lts.LTS().
            syn_set  -  Conjunto de eventos de sincronizacion. Instancia de la
                        clase Set() cuyos elementos son cadenas de caracteres.
        Retorno:
            True   -  Si los sistemas tienen eventos de sincronizacion en
                      comun / especificados en "sync_set".
            False  -  En caso contrario.
    """
    e1 = s1.get_event_set('o')
    e2 = s2.get_event_set('o')
    result = False
    if sync_set:
        if e1.intersection(e2).intersection(sync_set):
            result = True
        else:
            result = False
    else:
        if e1.intersection(e2):
            result = True
        else:
            result = False
    return result

def share_all_events (s1, s2, sync_set):
    """
        Descripcion:
            Esta funcion dice si dos sistemas comparten todos los eventos
            de sincronizacion dados en "sync_set".
        Argumentos:
            s1, s2    -  Instancias de la clase lts.LTS().
            sync_set  -  Conjunto de eventos de sincronizacion.
        Retorno:
            True   -  Si "s1" y "s2" comparten todos los eventos de
                        "sync_set".
            False  -  En caso contrario.
    """
    e1 = s1.get_event_set('o')
    e2 = s2.get_event_set('o')
    if sync_set.intersection(e1) == sync_set.intersection(e2):
        return True
    else:
        return False

#TODO: Move this as a method of the Stack class.
def stack_to_path (stack):
    """
        Descripcion:
            Convierte una pila (una lista en realidad) con determinado formato
            en un camino (una lista con otro formato).
        Argumentos:
            stack  -  Instancia de la clase List() cuyos elementos deben ser
                      3-uplas con el siguiente formato: (s,e,l) donde "s" es
                      una instancia de la clase State(), "e" es un evento
                      (cadena de caracteres) y "l" es una lista cuyo contenido
                      es ignorado.
        Retorno:
            Una instancia de la clase List() que representa un camino.
            La lista tiene el siguiente formato: [s0,e0,s1,e1,...] donde los
            elementos "s_i" son instancias de la clase State() y los elementos
            "e_i" son eventos (cadena de caracteres).
    """
    result = []
    for (s,e,l) in stack:
        if e != '':
            result.append(e)
        result.append(s)
    return result

# TODO: Create a class Path.
def path_to_string (path, system, maps_dict=None):
    """
        Descripcion:
            Convierte un camino (una lista con cierto formato) en una cadena de
            caracteres.
        Argumentos:
            path       -  Una instancia de la clase List() que representa un
                          camino. La lista tiene el siguiente formato:
                          [s0,e0,s1,e1,...] donde los elementos "s_i" son
                          instancias de la clase State() y los elementos
                          "e_i" son eventos (cadena de caracteres).
            system     -  Una instancia de la clase lts.LTS() a la cual el
                          camino "path" pertenece.
            maps_dict  -  Opcional. Es un diccionario de diccionarios.
                          Instancia de la clase Dict(), la cual tiene mapeos
                          de los nombres de los estados de "system". Los
                          datos de este diccionario deben ser los retornados
                          por la llamada a la funcion lts.to_file(), y las
                          claves deben ser los sistemas pasados como argumento
                          a lts.to_file(). En la forma mas simple de uso de
                          esta funcion con maps_dict!=None este  tiene el
                          siguiente formato: {system:lts.to_file(system)}
        Retorno:
            Una cadena de caracteres que representa al camino "path".
            El formato de esta cadena es el siguiente:
                "(n1) -> e1 -> (n2) -> e2 -> (n3) ... [name]"
            donde los "n_i" son los nombres de los estados y los "e_i" son los
            nombres de los eventos; "[name]" es el nombre del sistema al que
            pertenece el camino.
    """
    # Hago una copia del camino path.
    pathc = list(path)
    # Busco el mapeo para 'path'.
    if maps_dict:
        map_info = maps_dict[system]
    # Convierto a 'pathc' en un string
    for i in range(0,len(pathc)):
        if i%2 == 0:
            if maps_dict:
                state_name = map_info[str(pathc[i])]
            else:
                state_name = str(pathc[i])
            pathc[i] = '('+state_name+')'
        else:
            pathc[i] = '"'+str(pathc[i])+'"'
    separator = ' -> '
    return separator.join(pathc) + '\t[' + system.name + ']'

#TODO: Move this as a method of the class Path (when if exist).
def tp_to_sys_path (path, system, maps_dict=None):
    """
        Descripcion:
            Transforma un camino de una twinplant en la parte observable de dos
            caminos del sistema al que corresponde la twinplant.
        Argumentos:
            path       -  Una instancia de la clase List() que representa un
                          camino. La lista tiene el siguiente formato:
                          [s0,e0,s1,e1,...] donde los elementos "s_i" son
                          instancias de la clase State() y los elementos
                          "e_i" son eventos (cadena de caracteres).
            system     -  Una instancia de la clase lts.LTS() a la cual el
                          camino "path" pertenece.
            maps_dict  -  Opcional. Es un diccionario de diccionarios.
                          Instancia de la clase Dict(), la cual tiene mapeos
                          de los nombres de los estados de "system". Los
                          datos de este diccionario deben ser los retornados
                          por la llamada a la funcion lts.to_file(), y las
                          claves deben ser los sistemas pasados como argumento
                          a lts.to_file(). En la forma mas simple de uso de
                          esta funcion con maps_dict!=None este  tiene el
                          siguiente formato: {system:lts.to_file(system)}
        Retorno:
            Una una tupla de cadena de caracteres que representa a los camino
            izquiero y derecho contenidos en "path".
            El formato de estas cadenas es el siguiente:
                "(n1) -> e1 -> (n2) -> e2 -> (n3) ... [name]"
            donde los "n_i" son los nombres de los estados y los "e_i" son los
            nombres de los eventos; "[name]" es el nombre del sistema al que
            pertenece el camino.
    """
    # Hago copias de 'path'.
    pathL = list(path)
    pathR = list(path)
    # Busco el mapeo para 'system'.
    if maps_dict:
        map_info = maps_dict[system]
    # Convierto a 'path' en un string
    for i in range(0,len(path)):
        if i%2 == 0: # En los lugares pares estan los estados.
            if maps_dict:
                sL = (str(pathL[i].left_state).partition('#'))[0]
                sR = (str(pathR[i].right_state).partition('#'))[0]
                state_name_L = map_info[sL]
                state_name_R = map_info[sR]
            else:
                state_name_L = str(pathL[i].left_state)
                state_name_R = str(pathR[i].right_state)
            pathL[i] = '('+state_name_L+')'
            pathR[i] = '('+state_name_R+')'
        else: # En los lugares impares estan los eventos.
            pathL[i] = '"'+str(pathL[i])+'"'
            pathR[i] = '"'+str(pathR[i])+'"'
    separator = ' -> '
    r1 = separator.join(pathL)+'\t['+system.name+']'
    r2 = separator.join(pathR)+'\t['+system.name+']'
    return (r1,r2)


#TODO: This must be a method os the class LTS o FSM.
def graph (file):
    """
        Descripcion:
            Esta funcion utiliza el programa 'ltsgraph' para graficar un LTS
            almacenado en un archivo con formato 'Aldebaran'.
        Argumentos:
            file  -  Una cadena de caracteres con la ruta al archivo del
                     sistema que se quiere graficar.
        Retorno:
            No tiene.
    """
    # Busco el ejecutable en el path.
    ltsgraph = ''
    if sys.platform == 'win32':
        for path in os.getenv('PATH').split(';'):
            if not path.endswith('\\'): path += '\\'
            path += "ltsgraph.exe"
            if os.path.isfile(path):
                ltsgraph = path
                break
    else:
        for path in os.getenv('PATH').split(':'):
            if not path.endswith('/'): path += '/'
            path += "ltsgraph"
            if os.path.isfile(path):
                ltsgraph = path
                break
    if not file.endswith('.aut'):
        file += '.aut'
    args = ["ltsgraph.exe", file]
    if ltsgraph != '':
        os.spawnv(os.P_NOWAIT, ltsgraph, args)
